package com.mbc.hr.manager;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.TemporalType;

import com.mbc.common.CommonSingleton;
import com.mbc.common.entities.SystemProperty;
import com.mbc.common.entities.User;
import com.mbc.common.exception.DataNotFoundException;
import com.mbc.common.manager.AbstractMgr;
import com.mbc.common.utils.EmailUtil;
import com.mbc.common.utils.KeyGenerator;
import com.mbc.hr.data.HRContants;
import com.mbc.hr.entities.ApprovedLeave;
import com.mbc.hr.entities.AttendancePolicyDetail;
import com.mbc.hr.entities.CalendarEvent;
import com.mbc.hr.entities.CalendarEventParticipant;
import com.mbc.hr.entities.CalendarEventReminder;
import com.mbc.hr.entities.Employee;
import com.mbc.hr.entities.EmployeeGroup;
import com.mbc.hr.entities.Holiday;
import com.mbc.hr.entities.Leave;
import com.mbc.hr.entities.LeaveBalance;
import com.mbc.hr.entities.LeaveSchemeDetail;
import com.mbc.hr.entities.LeaveType;
import com.mbc.hr.exception.ExistingLeaveException;
import com.mbc.hr.exception.NotEmployeeException;
import com.mbc.hr.exception.NotEnoughLeaveBalanceException;

public class CalendarEventMgr extends AbstractMgr {
	
	public CalendarEventMgr(){}
	
	public CalendarEventMgr(String sessionId){
		this.sessionId = sessionId;
	}
	
	public static void getMonthStartNEnd(Date date, Date start, Date end)throws Exception{
		try {
//			start = new Date();
//			end = new Date();
			Calendar l_calendar = Calendar.getInstance();
			l_calendar.setTime(date);
			
			Calendar l_cal = Calendar.getInstance();
			l_cal.set(l_calendar.get(Calendar.YEAR), l_calendar.get(Calendar.MONTH), 1);
			start.setTime(l_cal.getTime().getTime());
			l_cal.add(Calendar.MONTH, 1);
			l_cal.add(Calendar.DAY_OF_MONTH, -1);
			end.setTime(l_cal.getTime().getTime());
		} catch (Exception e) {
			throw e;
		}
	}
	
	public static double getTotalDays(Date start, Date end, long attendancePolicyId, EntityManager em)throws Exception{
		
		Calendar l_startCanendar = Calendar.getInstance();
		l_startCanendar.setTime(start);
		Calendar l_endCalendar = Calendar.getInstance();
		l_endCalendar.setTime(end);
		
		List<AttendancePolicyDetail> l_attendancePolicyDetails = em.createNamedQuery("AttendancePolicyDetail.findByPolicyID", AttendancePolicyDetail.class)
				.setParameter("policyID", attendancePolicyId)
				.getResultList();
		
		int l_totalWorkingDays = 0;
		int l_dayDifference = (int)((end.getTime() - start.getTime())+86400000) / 86400000;
		if(l_dayDifference < 8){
			int l_weekDay = l_startCanendar.get(Calendar.DAY_OF_WEEK);
			for (int i = 0; i < l_dayDifference; i++) {
				for (AttendancePolicyDetail l_policyDetail : l_attendancePolicyDetails) {
					if(l_weekDay == l_policyDetail.getDay()){
						l_totalWorkingDays++;
						break;
					}
				}
				l_weekDay++;
			}
		}else{
			int l_totalWeeks = ((int)((end.getTime() - start.getTime()) + 86400000)/86400000)/7;
			int l_extraDays = ((int)((end.getTime() - start.getTime()) + 86400000)/86400000)%7;
			
			l_totalWorkingDays= l_attendancePolicyDetails.size() * l_totalWeeks;
			if(l_extraDays > 0){
				int l_startDayOfWeek = l_startCanendar.get(Calendar.DAY_OF_WEEK);
				if(l_startDayOfWeek != Calendar.SUNDAY){
					while(l_startDayOfWeek < 8){
						for (AttendancePolicyDetail l_policyDetail : l_attendancePolicyDetails) {
							if(l_startDayOfWeek == l_policyDetail.getDay()){
								l_totalWorkingDays++;
								break;
							}
						}
						l_startDayOfWeek++;
					}
				}
				int l_endDayOfWeek = l_endCalendar.get(Calendar.DAY_OF_WEEK);
				if(l_endDayOfWeek != Calendar.SATURDAY){
					while(l_endDayOfWeek > 0){
						for (AttendancePolicyDetail l_policyDetail : l_attendancePolicyDetails) {
							if(l_endDayOfWeek == l_policyDetail.getDay()){
								l_totalWorkingDays++;
								break;
							}
							l_endDayOfWeek--;
						}
					}
				}
			}
			
			List<Holiday> l_holidays = em.createNamedQuery("Holiday.findBetween", Holiday.class)
					.setParameter("startDate", start, TemporalType.DATE)
					.setParameter("endDate", end, TemporalType.DATE)
					.getResultList();
			for (Holiday l_holiday : l_holidays) {
				l_startCanendar.setTime(l_holiday.getHolidayDate());
				for (AttendancePolicyDetail l_policyDetail : l_attendancePolicyDetails) {
					if(l_startCanendar.get(Calendar.DAY_OF_WEEK) == l_policyDetail.getDay()){
						l_totalWorkingDays--;
						break;
					}
				}
			}
		}
		return l_totalWorkingDays;
	}
	
	public void createEvent(CalendarEvent event, String loginId)throws Exception{
		openConnection();
		try {
			beginTransaction();
			event.setEventID(KeyGenerator.generateKey(entityManager));
			
			if(event.getEventType() == 4){
				Employee l_emp = null;
				try {
					l_emp = entityManager.createNamedQuery("Employee.findByEmployeeNo", Employee.class)
					.setParameter("employeeNo", loginId)
					.getSingleResult();
				} catch (NoResultException innerEx) {
					throw new NotEmployeeException("This login[" + loginId + "] user is not an employee.");
				}
				SystemProperty l_SysProp = entityManager.find(SystemProperty.class, "calendar_leave");
				if(l_SysProp == null)
					throw new DataNotFoundException("Default Calendar Leave has not been set.");
				
				EmployeeGroup l_empGroup = entityManager.find(EmployeeGroup.class, l_emp.getEmployeeGroupID());
				Leave l_leave = new Leave();
				l_leave.setCreatedDate(new Date());
				l_leave.setCreatedBy(event.getCreatedBy());
				l_leave.setEmployeeID(l_emp.getEmployeeID());
				l_leave.setEmployeeName(l_emp.getName());
				l_leave.setReason(event.getEventName());
				l_leave.setEndDateTime(event.getStartDateTime());
				l_leave.setReason(event.getEventName());
				l_leave.setEventID(event.getEventID());
				l_leave.setLeaveID(KeyGenerator.generateKey(entityManager));
				l_leave.setLeaveTypeID(Long.parseLong(l_SysProp.getValue()));
				l_leave.setStartDateTime(event.getStartDateTime());
				l_leave.setTotalDays(getTotalDays(l_leave.getStartDateTime(), l_leave.getEndDateTime(), l_empGroup.getAttendancePolicyID(), entityManager));
				
				LeaveType l_leaveType = entityManager.find(LeaveType.class, l_leave.getLeaveTypeID());
				l_leave.setLeaveDescription(l_leaveType.getName());
				
				List<Leave> l_pendingLeaves = entityManager.createNamedQuery("Leave.findOverlapped", Leave.class)
					.setParameter("startDateTime", l_leave.getStartDateTime())
					.setParameter("endDateTime", l_leave.getEndDateTime())
					.setParameter("employeeID", l_emp.getEmployeeID())
					.getResultList();
				if(l_pendingLeaves.size() > 0)
					throw new ExistingLeaveException("There is a pending leave on " + l_leave.getStartDateTime() + ".");
				
				List<ApprovedLeave> l_ApprovedLeaves = entityManager.createNamedQuery("ApprovedLeave.findOverlapped", ApprovedLeave.class)
						.setParameter("startDateTime", l_leave.getStartDateTime())
						.setParameter("endDateTime", l_leave.getEndDateTime())
						.setParameter("employeeID", l_emp.getEmployeeID())
						.setParameter("status", HRContants.LeaveStatus.APPROVED)
						.getResultList();
				if(l_ApprovedLeaves.size() > 0)
					throw new ExistingLeaveException("There is a approved leave on " + l_leave.getStartDateTime() + ".");
				
				LeaveBalance l_leaveBalance = null;
				Calendar l_cal = Calendar.getInstance();
				l_cal.setTime(l_leave.getStartDateTime());
				try {
					l_leaveBalance = entityManager.createNamedQuery("LeaveBalance.findByEmployeeIdLeaveTypeYear", LeaveBalance.class)
						.setParameter("employeeID", l_leave.getEmployeeID())
						.setParameter("leaveTypeID", l_leave.getLeaveTypeID())
						.setParameter("year", l_cal.get(Calendar.YEAR))
						.getSingleResult();
				} catch (NoResultException ex) {
					List<LeaveSchemeDetail> l_details = entityManager.createNamedQuery("LeaveSchemeDetail.findAllByLeaveScheme", LeaveSchemeDetail.class)
							.setParameter("leaveSchemeID", l_empGroup.getLeaveSchemeID())
							.getResultList();
					Calendar l_lsCalendar = Calendar.getInstance();
					l_lsCalendar.setTime(l_leave.getStartDateTime());
					int l_year = l_lsCalendar.get(Calendar.YEAR);
					for (LeaveSchemeDetail l_detail : l_details) {
						LeaveBalance l_balance = new LeaveBalance();
						l_balance.setEmployeeID(l_emp.getEmployeeID());
						l_balance.setLeaveTypeID(l_detail.getLeaveTypeID());
						l_balance.setYear(l_year);
						l_balance.setMaximum(l_detail.getDaysPerYear());
						l_balance.setTotal(0);
						entityManager.persist(l_balance);
						
						if(l_balance.getLeaveTypeID() == l_leave.getLeaveTypeID())
							l_leaveBalance = l_balance;
					}
					entityManager.flush();
				}
				if((l_leaveBalance.getMaximum() - l_leaveBalance.getTotal()) < l_leave.getTotalDays()){
					throw new NotEnoughLeaveBalanceException("Not enought leaves.");
				}
				LeaveSchemeDetail l_leaveSchemeDetail = entityManager.createNamedQuery("LeaveSchemeDetail.findByLeaveSchemeLeaveType", LeaveSchemeDetail.class)
						.setParameter("leaveSchemeID", l_empGroup.getLeaveSchemeID())
						.setParameter("leaveTypeID", l_leave.getLeaveTypeID())
						.getSingleResult();
				if(l_leaveSchemeDetail.getDaysPerMonth() != 0){
					Date l_start = null;
					Date l_end = null;
					CalendarEventMgr.getMonthStartNEnd(l_leave.getStartDateTime(), l_start, l_end);
					long l_count = entityManager.createNamedQuery("ApprovedLeave.getTotalLeavePerMonth", Long.class)
							.setParameter("status", HRContants.LeaveStatus.APPROVED)
							.setParameter("employeeID", l_emp.getEmployeeID())
							.setParameter("startDateTime", l_start)
							.setParameter("endDateTime", l_end)
							.setParameter("leaveTypeID", l_leave.getLeaveTypeID())
							.getSingleResult();
					if(l_count >= l_leaveSchemeDetail.getDaysPerMonth())
						throw new NotEnoughLeaveBalanceException("Not enought leaves for this month.", NotEnoughLeaveBalanceException.MONTH);
				}
				
				Employee l_supervisor = entityManager.find(Employee.class, l_emp.getSupervisor());
				if(l_supervisor != null && !l_supervisor.getEmail().equals("")){
					SystemProperty l_userProp = entityManager.find(SystemProperty.class, "mail_user");
					SystemProperty l_pwdProp = entityManager.find(SystemProperty.class, "mail_password");
					if(l_userProp != null && l_pwdProp != null){
						try {
							LeaveType l_LeaveType = entityManager.find(LeaveType.class, l_leave.getLeaveTypeID()); 
							String l_fileName = CommonSingleton.ABSOLUTE_PATH + "data\\email_templates\\apply_leave.html";
							SimpleDateFormat l_dateTimeFormat = new SimpleDateFormat("hh:mm a d MMM yyyy");
							HashMap<String, String> l_parameters = new HashMap<String, String>();
							l_parameters.put("{name}", l_supervisor.getName());
							l_parameters.put("{applied_name}", l_emp.getName());
							l_parameters.put("{leave_type}", l_LeaveType.getName());
							l_parameters.put("{event}", l_leave.getReason());
							l_parameters.put("{start}", l_dateTimeFormat.format(l_leave.getStartDateTime()));
							l_parameters.put("{end}", l_dateTimeFormat.format(l_leave.getEndDateTime()));
							EmailUtil.sendWithHTMLTemplate(l_supervisor.getEmail(), "Reminder for the event[" + event.getEventName() + "]", l_userProp.getValue(), l_pwdProp.getValue(), l_fileName, l_parameters);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
				entityManager.persist(l_leave);
			}
			
			if(event.getEndDateTime() != null){
				SimpleDateFormat l_fmt = new SimpleDateFormat("HHmmss");
				if(l_fmt.format(event.getEndDateTime()).equals("000000")){
					Calendar l_calendar = Calendar.getInstance();
					l_calendar.setTime(event.getEndDateTime());
					l_calendar.add(Calendar.DAY_OF_MONTH, 1);
					event.setEndDateTime(l_calendar.getTime());
				}
			}
			
			Calendar l_calendar = Calendar.getInstance();
			l_calendar.setTime(event.getStartDateTime());
			l_calendar.set(Calendar.SECOND, 0);
			l_calendar.set(Calendar.MILLISECOND, 0);
			event.setStartDateTime(l_calendar.getTime());
			entityManager.persist(event);
			for (CalendarEventReminder l_detail : event.getReminders()) {
				l_detail.setEventID(event.getEventID());
				entityManager.persist(l_detail);
			}
			
			User l_user = entityManager.find(User.class, event.getCreatedBy());
			
			CalendarEventParticipant l_creator = new CalendarEventParticipant();
			l_creator.setEventID(event.getEventID());
			l_creator.setParticipant(l_user.getUserName());
			l_creator.setUserId(event.getCreatedBy());
			l_creator.setEditable(true);
			event.getParticipants().add(l_creator);
			
			for (CalendarEventParticipant l_participant : event.getParticipants()) {
				l_participant.setEventID(event.getEventID());
				entityManager.persist(l_participant);
			}
			
			commitTransaction();
		} catch (Exception e) {
			rollbackTransaction();
			throw e;
		} finally {
			closeConnection();
		}
	}
	
	public void updateEvent(CalendarEvent event)throws Exception{
		openConnection();
		try {
			beginTransaction();
			if(event.getEndDateTime() != null){
				SimpleDateFormat l_fmt = new SimpleDateFormat("HHmmss");
				if(l_fmt.format(event.getEndDateTime()).equals("000000")){
					Calendar l_calendar = Calendar.getInstance();
					l_calendar.setTime(event.getEndDateTime());
					l_calendar.add(Calendar.DAY_OF_MONTH, 1);
					event.setEndDateTime(l_calendar.getTime());
				}
			}
			entityManager.merge(event);
			entityManager.createNamedQuery("CalendarEventReminder.deleteByEventID").setParameter("eventID", event.getEventID()).executeUpdate();
			entityManager.createNamedQuery("CalendarEventParticipant.deleteByEventID").setParameter("eventID", event.getEventID()).executeUpdate();
			
			for (CalendarEventReminder l_detail : event.getReminders()) {
				l_detail.setEventID(event.getEventID());
				entityManager.persist(l_detail);
			}
			
			User l_user = entityManager.find(User.class, event.getCreatedBy());
			
			CalendarEventParticipant l_creator = new CalendarEventParticipant();
			l_creator.setEventID(event.getEventID());
			l_creator.setParticipant(l_user.getUserName());
			l_creator.setUserId(event.getCreatedBy());
			l_creator.setEditable(true);
			event.getParticipants().add(l_creator);
			
			for (CalendarEventParticipant l_participant : event.getParticipants()) {
				l_participant.setEventID(event.getEventID());
				entityManager.persist(l_participant);
			}
			
			commitTransaction();
		} catch (Exception e) {
			rollbackTransaction();
			throw e;
		} finally {
			closeConnection();
		}
	}
	
	public void deleteEvent(long key)throws Exception{
		openConnection();
		try {
			beginTransaction();
			
			CalendarEvent l_event = entityManager.find(CalendarEvent.class, key);
			if(l_event.getEventType() == 4){
				
				entityManager.createNamedQuery("Leave.deleteByEventID").setParameter("eventID", key).executeUpdate();
			}
			entityManager.remove(l_event);
			
			entityManager.createNamedQuery("CalendarEventReminder.deleteByEventID").setParameter("eventID", key).executeUpdate();
			entityManager.createNamedQuery("CalendarEventParticipant.deleteByEventID").setParameter("eventID", key).executeUpdate();
			
			commitTransaction();
		} catch (Exception e) {
			rollbackTransaction();
			throw e;
		} finally {
			closeConnection();
		}
	}
	
	public List<CalendarEvent> getEvents(Date startDate, Date endDate, long createdBy)throws Exception{
		openConnection();
		try {
			List<Object[]> l_results = entityManager.createNamedQuery("CalendarEvent.findAll", Object[].class)
					.setParameter("startDate", startDate)
					.setParameter("endDate", endDate)
					.setParameter("createdBy", createdBy)
					.setHint("org.hibernate.cacheable", false)
					.getResultList();
			List<CalendarEvent> l_calendarEventList = new ArrayList<CalendarEvent>();
			for (Object[] l_result : l_results) {
				CalendarEvent l_event = (CalendarEvent)l_result[0];
				l_event.setEditable(Boolean.parseBoolean(l_result[1].toString()));
				l_calendarEventList.add(l_event);
			}
			return l_calendarEventList;
		} catch (Exception e) {
			throw e;
		} finally {
			closeConnection();
		}
	}
	
	public CalendarEvent find(long key)throws Exception{
		openConnection();
		try {
			CalendarEvent l_event = entityManager.find(CalendarEvent.class, key);
			if(l_event != null){
				SimpleDateFormat l_fmt = new SimpleDateFormat("HHmmss");
				if(l_event.getEndDateTime() != null){
					if(l_fmt.format(l_event.getEndDateTime()).equals("000000")){
						Calendar l_calendar = Calendar.getInstance();
						l_calendar.setTime(l_event.getEndDateTime());
						l_calendar.add(Calendar.DAY_OF_MONTH, -1);
						l_event.setEndDateTime(l_calendar.getTime());
					}
				}
			}
			l_event.setReminders(entityManager.createNamedQuery("CalendarEventReminder.findAllByEventID", CalendarEventReminder.class)
					.setParameter("eventID", key)
					.getResultList());
			
			l_event.setParticipants(entityManager.createNamedQuery("CalendarEventParticipant.findAll", CalendarEventParticipant.class)
					.setParameter("eventID", key)
					.setParameter("userId", l_event.getCreatedBy())
					.getResultList());
			
			return l_event;
		} catch (Exception e) {
			throw e;
		} finally {
			closeConnection();
		}
	}
}
